Skip to content

Conversation

@bobtista
Copy link

@bobtista bobtista commented Jan 21, 2026

Summary

  • Objects were saved oldest-first but loaded via prepending, which reversed their order
  • New saves now write objects in reverse order (newest first) so they load correctly
  • Old saves have their object list reversed after loading to fix the order
  • Bumped xfer version to 11 (Zero Hour) and 10 (Generals) for backwards compatibility

Test plan

  • Load an existing save from before this change, verify objects behave correctly
  • Create a new save, reload it, verify object order matches the original session
  • Verify game runs normally

Todo:

  • Replicate to Generals

…nal save order

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@greptile-apps
Copy link

greptile-apps bot commented Jan 21, 2026

Greptile Overview

Greptile Summary

This PR fixes a long-standing bug where object order was reversed after loading saves. Objects are prepended to the list during creation (newest at head), but were saved oldest-first, causing the order to reverse when loaded.

Key Changes:

  • Objects now saved in reverse order (newest-first) so they load correctly via prepending
  • Backwards compatibility: old saves have their object list reversed after loading
  • Version bumped to 10 (Generals) and 11 (Zero Hour) to distinguish new format
  • Added getPrevObject() and friend setters to Object class for list manipulation

Minor Issues:

  • Style violation: loop body on same line as condition in save logic (lines 4288-4289 in Generals, 4847-4848 in Zero Hour)

Confidence Score: 4/5

  • Safe to merge with minor style issue that should be addressed
  • The logic is sound and correctly implements both forward compatibility (new saves) and backwards compatibility (old saves). The linked list reversal algorithm is correct. Only issue is a minor style violation with single-line loop bodies that affects debugger usability but not functionality.
  • Pay attention to the GameLogic.cpp files which contain the style violations (loop bodies should be on separate lines)

Important Files Changed

Filename Overview
Generals/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp Implemented reverse-order save and backwards compatibility fix for old saves. Contains a style violation with single-line loop body
GeneralsMD/Code/GameEngine/Source/GameLogic/System/GameLogic.cpp Implemented reverse-order save and backwards compatibility fix for old saves. Contains a style violation with single-line loop body

Sequence Diagram

sequenceDiagram
    participant Game as Game Save/Load
    participant GL as GameLogic::xfer()
    participant OL as Object List
    participant Obj as Object

    alt SAVE Mode (Version 10+/11+)
        Game->>GL: xfer(XFER_SAVE)
        GL->>OL: Traverse forward to find last object
        loop For each object (reverse order)
            GL->>Obj: getPrevObject()
            GL->>Obj: xfer object data
        end
        Note over GL,Obj: Objects saved newest-first
    else LOAD Mode
        Game->>GL: xfer(XFER_LOAD)
        loop For each saved object
            GL->>Obj: Create new object
            Note over Obj: prependToList() adds to head
            GL->>Obj: xfer object data
        end
        alt Old Save (Version ≤9 or ≤10)
            Note over GL,OL: Objects loaded in wrong order
            GL->>OL: Reverse entire object list
            loop Reverse linked list
                OL->>Obj: Swap next/prev pointers
            end
            Note over GL,OL: Order corrected
        else New Save (Version 10+/11+)
            Note over GL,OL: Objects already in correct order
        end
    end
Loading

@Stubbjax
Copy link

Nice. You'd be also able to test this by saving a game with two Command Centers, then pressing H and expecting to navigate to the same Command Center before and after loading a save.

@Caball009
Copy link

Caball009 commented Jan 21, 2026

Nice. You'd be also able to test this by saving a game with two Command Centers, then pressing H and expecting to navigate to the same Command Center before and after loading a save.

That's not related to this change, though the underlying issue is the same.

You can uncomment this code to get the same behavior for H before and after loading a save:

// since we prepended the object member pointers, reverse that list so it's just like before
// reverse_TeamMemberList();

@Mauller
Copy link

Mauller commented Jan 21, 2026

Could reverse iterating during saving also fix this?

@Caball009
Copy link

Caball009 commented Jan 21, 2026

Could reverse iterating during saving also fix this?

I think so. That wouldn't work with already existing save games, though. Also, I think it requires changes to multiple functions.

@xezon
Copy link

xezon commented Jan 22, 2026

This is a good find.

Could reverse iterating during saving also fix this?

I support this. Fixing the order of objects after creation is alright, but creating the objects in the correct order to begin with will be superior, because that will respect the right ordering even during creation, which hopefully is logically irrelevant, but technically more correct.

@xezon xezon added Bug Something is not working right, typically is user facing Major Severity: Minor < Major < Critical < Blocker Gen Relates to Generals ZH Relates to Zero Hour Fix Is fixing something, but is not user facing and removed Bug Something is not working right, typically is user facing labels Jan 22, 2026
@Caball009
Copy link

Caball009 commented Jan 26, 2026

The code actually get simpler if you do the work when saving. This will only work properly for new save games.

for( obj = getFirstObject(); obj; obj = obj->getNextObject() )

->

Object *lastObj = nullptr;
for (obj = getFirstObject(); obj; obj = obj->getNextObject())
{
		lastObj = obj;
}

for( obj = lastObj; obj; obj = obj->getPrevObject() )

New Object member functions:

Object* getPrevObject() { return m_prev; }
const Object* getPrevObject() const { return m_prev; }

@xezon
Copy link

xezon commented Jan 26, 2026

Good damn single linked list 😄

@Caball009
Copy link

Does it make sense to increase the xfer version for this? It would give us a way to distinguish new save games with inverse object list if we ever needed to.

@xezon
Copy link

xezon commented Jan 27, 2026

Yes can do. Is not strictly necessary but fair to do as you have described.

@OmniBlade
Copy link

I'd be inclined to do both, reverse the list on load if the xfer version is retail, save with correct order with new version and load as is if version is same or greater than new version.

@bobtista bobtista changed the title bugfix(savegame): Restore object list order after load to match original save order bugfix(savegame): Fix object list order after load Jan 27, 2026

// version
const XferVersion currentVersion = 9;
const XferVersion currentVersion = 10;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume RETAIL_COMPATIBLE_XFER_SAVE is not strictly necessary? Retail game can still load these save games correctly?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if an old game tries to load a new save it will just fail gracefully. I don't think this will be an issue - if someone asks a friend with TSH to create a .sav and share it with them maybe? But why would that ever happen? If we need to be able to load new saves on old game, we could use RETAIL_COMPATIBLE_XFER_SAVE

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I think the way it would work is that version 10 isn't known by retail and isn't checked so it will load the list the same way it always did which will be the new correct order for saves saved after this version, the old incorrect order for save from before this. This new version checks on load so the order loaded will be correct for builds after this PR is merged no matter what version wrote the save.


}

// TheSuperHackers @bugfix bobtista 27/01/2026 Reverse object list for old saves.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would label this change a fix, as they're not user-facing.

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Copy link

@xezon xezon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks correct. See suggestion from Caball regarding comment tag.

There are a bunch of TODO's in the opening description.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Fix Is fixing something, but is not user facing Gen Relates to Generals Major Severity: Minor < Major < Critical < Blocker ZH Relates to Zero Hour

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants